package com.ben.multiple.aop.aspect;

import com.ben.multiple.aop.annotation.TestAnnotation3;
import com.ben.multiple.aop.annotation.TestAnnotation4;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class TestAspect {

    /**
     * 定义一个公用的 切点；供下面的 advice 使用
     * execution == 返回数据类型 包名.类名.方法名(参数类型)
     * 第一个 * 号：返回的数据类型；任意类型。
     * 类路径：com.ben.multiple.aop.rest.OneRest
     * 方法签名：com.ben.multiple.aop.rest.OneRest.a(..)
     * (..)     括号内是参数类型，   ..  表示任意参数
     * <p>
     * 类中的所有方法 ：com.ben.multiple.aop.rest.OneRest.*
     * com.ben.multiple.aop.rest.    此包路径下的类
     * com.ben.multiple.aop.rest..  此包及其子包路径下的类
     * com.ben.multiple.aop.rest.*   * 表示此包路径下的所有类
     */
    @Pointcut(value = "execution(* com.ben.multiple.aop.rest.OneRest.a(..))")
    public void pointcut1() {
    }

    /**
     * 前置通知：目标方法执行之前执行以下方法体的内容
     *
     * @param point 连接点
     */
    @Before(value = "pointcut1()")
    public void beforePointcut1(JoinPoint point) {
        Signature signature = point.getSignature();
        log.info("TestAspect  beforePointcut1 :: signature == {}", signature);
    }

    /**
     * 目标对象被代理之后，生成的代理对象 和 指定的类型匹配才会被拦截
     * com.ben.multiple.aop.rest.TwoRest   目标对象是此类型时，会被拦截
     */
    @Pointcut(value = "this(com.ben.multiple.aop.rest.TwoRest)")
    public void pointcut2() {
    }

    @Before(value = "pointcut2()")
    public void beforePointcut2(JoinPoint point) {
        Signature signature = point.getSignature();
        log.info("TestAspect  beforePointcut2 :: signature == {}", signature);
    }

    /**
     * 目标对象是指定对象时会被拦截
     * this作用于代理对象，target作用于目标对象
     * this表示目标对象被代理之后生成的代理对象和指定的类型匹配会被拦截，匹配的是代理对象
     * target表示目标对象和指定的类型匹配会被拦截，匹配的是目标对象
     */
    @Pointcut(value = "target(com.ben.multiple.aop.rest.ThreeRest)")
    public void pointcut3() {
    }

    @Before(value = "pointcut3()")
    public void beforePointcut3(JoinPoint point) {
        Signature signature = point.getSignature();
        log.info("TestAspect  beforePointcut3 :: signature == {}", signature);
    }

    /**
     * 匹配有指定注解的类
     * 注解作用在类上
     */
    @Pointcut(value = "@within(com.ben.multiple.aop.annotation.TestAnnotation1)")
    public void pointcut4() {
    }

    @Before(value = "pointcut4()")
    public void beforePointcut4(JoinPoint point) {
        Signature signature = point.getSignature();
        log.info("TestAspect  beforePointcut4 :: signature == {}", signature);
    }

    /**
     * 匹配有指定注解的方法
     * 注解作用在方法上
     */
    @Pointcut(value = "@annotation(com.ben.multiple.aop.annotation.TestAnnotation2)")
    public void pointcut5() {
    }

    @Before(value = "pointcut5()")
    public void beforePointcut5(JoinPoint point) {
        Signature signature = point.getSignature();
        log.info("TestAspect  beforePointcut5 :: signature == {}", signature);
    }


    @Pointcut(value = "execution(* com.ben.multiple.aop.rest.SixRest.*(..))")
    public void pointcut6() {
    }

    /**
     * After ::不管是抛出异常或者正常退出都会执行
     */
    @After(value = "pointcut6()")
    public void afterPointcut6(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        log.info("TestAspect  afterPointcut6 :: target == {}", target);
    }

    @Pointcut(value = "execution(* com.ben.multiple.aop.rest.SevenRest.*(..))")
    public void pointcut7() {
    }

    /**
     * AfterReturning :: 方法正常退出时才执行
     */
    @AfterReturning(value = "pointcut7()")
    public void afterReturningPointcut7(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        log.info("TestAspect  afterReturningPointcut7 :: target == {}", target);
    }

    /**
     * AfterThrowing :: 方法抛出异常时执行
     */
    @AfterThrowing(value = "pointcut7()")
    public void afterThrowingPointcut7(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        log.info("TestAspect  afterThrowingPointcut7 :: target == {}", target);
    }


    @Pointcut(value = "@annotation(com.ben.multiple.aop.annotation.TestAnnotation3))")
    public void pointcut9() {
    }

    @Around(value = "pointcut9()")
    public Object aroundPointcut9(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getInvokeMethod(joinPoint);
        String methodName = method.getName();
        log.info("TestAspect aroundPointcut9 :: methodName :: {}", methodName);
        String value = method.getAnnotation(TestAnnotation3.class).value();
        log.info("TestAspect aroundPointcut9 :: TestAnnotation3 :: {}", value);
        log.info("方法执行之前");
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info("方法执行之后，用时 毫秒：{}", (end - start));
        log.info("执行结果 ： {}", result);
        return result;
    }

    private Method getInvokeMethod(ProceedingJoinPoint joinPoint) throws ReflectiveOperationException {
        String methodName = joinPoint.getSignature().getName();
        String fullName = joinPoint.getSignature().toLongString();
        String argsType = fullName.substring(fullName.indexOf("(") + 1, fullName.lastIndexOf(")"));
        if ("".equals(argsType)) {
            return joinPoint.getTarget().getClass().getMethod(methodName);
        }
        String[] split = argsType.split(",");
        List<Class<?>> argsClass = new ArrayList<>();
        for (String str : split) {
            argsClass.add(Class.forName(str));
        }
        return joinPoint.getTarget().getClass().getMethod(methodName, argsClass.toArray(new Class[0]));
    }

    /**
     * @param annotation4 注解对象 ：：对应于 around 里面的注解参数；方法上有注解 TestAnnotation4 的地方会被拦截
     */
    @Around(value = "@annotation(annotation4)")
    public Object aroundPointcut(ProceedingJoinPoint joinPoint, TestAnnotation4 annotation4) throws Throwable {
        String value = annotation4.value();
        log.info("TestAspect aroundPointcut :: TestAnnotation4 :: {}", value);
        System.out.println("方法执行之前");
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info("方法执行之后，用时 毫秒：{}", (end - start));
        log.info("执行结果 ： {}", result);
        return result;
    }
}
